using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

/* Урок 1. "Построение графиков. Элементы интерполяции." */
 
namespace Interpolation2D
{
    public partial class frmMain : Form
    {
        public frmMain()
        {
            InitializeComponent();
        }

        #region === memebers ===

        Bitmap _bitmap = null;
        PointF[] _curve = null;
        Interpolation2D _chart2D = null;

        #endregion

        #region === private ===

        static double[] DataXForInterpolation => new double[] { 20, 50, 90, 130, 160, 190, 210 };

        static double[] DataYForInterpolation => new double[] { 20, 40, 50, 44, 40, 56, 84 };

        /// <summary>
        /// Получить прямоугольник в оконной системе координат, где будет отображаться график.
        /// </summary>
        /// <param name="widthWin">Ширина окна.</param>
        /// <param name="heightWin">Высота окна.</param>
        /// <param name="XminReal">Минимальное значение по Х в реальной системе координат.</param>
        /// <param name="XmaxReal">Максимальное значение по Х в реальной системе координат.</param>
        /// <param name="YminReal">Минимальное значение по Y в реальной системе координат.</param>
        /// <param name="YmaxReal">Максимальное значение по Y в реальной системе координат.</param>
        /// <returns>Возвращается прямоугольник в оконной системе координат, где будет отображаться график.</returns>
        RectangleF GetBoundRectWin(int widthWin, int heightWin, double XminReal, double XmaxReal, double YminReal, double YmaxReal)
        {
            const int c_margin = 36;
            float x, y, width, height;

            float zoom = Convert.ToSingle(XmaxReal - XminReal) / Convert.ToSingle(YmaxReal - YminReal);
            width = widthWin - 2 * c_margin;
            height = width / zoom;

            if (height > heightWin - 2 * c_margin)
            {
                height = heightWin - 2 * c_margin;
                width = height * zoom;
                x = (widthWin - width) / 2;
                y = c_margin;
            }
            else
            {
                x = c_margin;
                y = (heightWin - height) / 2;
            }

            return new RectangleF(x, y, width, height);
        }

        /// <summary>
        /// Получение списка кривых для отображения в оконной системе координат.
        /// </summary>
        /// <param name="f">Перемнная типа FunctionDescription с описание функции.</param>
        /// <param name="widthWin">Ширина окна.</param>
        /// <param name="heightWin">Высота окна.</param>
        /// <returns>Возвращает список кривых (в случае разрывной функции) для отображения в оконной системе координат.</returns>
        PointF[] GetInterpolation2D(InterpolationDescription f, int widthWin, int heightWin)
        {
            _chart2D = new Interpolation2D(f);
            RectangleF boundRect = GetBoundRectWin(widthWin, heightWin, _chart2D.MinX, _chart2D.MaxX, _chart2D.MinY, _chart2D.MaxY);

            Transformer transformer = new Transformer
            {
                XminReal = _chart2D.MinX,
                XmaxReal = _chart2D.MaxX,
                YminReal = _chart2D.MinY,
                YmaxReal = _chart2D.MaxY,
                XminWin = boundRect.Left,
                XmaxWin = boundRect.Right,
                YminWin = boundRect.Top,
                YmaxWin = boundRect.Bottom,
                Optimaze = true
            };
            
            return transformer.Transform(_chart2D.curve);
        }

        void RenderGrid(Graphics g)
        {
            RectangleF boundRect = GetBoundRectWin(pictureBox1.Width, pictureBox1.Height, _chart2D.MinX, _chart2D.MaxX, _chart2D.MinY, _chart2D.MaxY);
            CoordinateGrid.Render(g, boundRect, _chart2D.MinX, _chart2D.MaxX, _chart2D.MinY, _chart2D.MaxY);
        }

        void RenderChart(Graphics g)
        {
            if (_curve == null)
                return;

            GraphicsState graphicsState = g.Save();
            g.SmoothingMode = SmoothingMode.AntiAlias;
            g.DrawLines(new Pen(Color.Red, 2.0f), _curve);
            g.Restore(graphicsState);
        }

        void RenderControlPoints(Graphics g)
        {
            InterpolationDescription fd = cmbFuncs.SelectedItem as InterpolationDescription;
            RectangleF br = GetBoundRectWin(pictureBox1.Width, pictureBox1.Height, _chart2D.MinX, _chart2D.MaxX, _chart2D.MinY, _chart2D.MaxY);
            const int w = 3;
            var seq = fd.X.Zip(fd.Y, (x, y) => Transformer.ConvertToWin(x, y, br, _chart2D.MinX, _chart2D.MaxX, _chart2D.MinY, _chart2D.MaxY));
            RectangleF[] ra = Array.ConvertAll(seq.ToArray(), p => new RectangleF(p.X - w, p.Y - w, 2 * w + 1, 2 * w + 1));

            g.FillRectangles(Brushes.Yellow, ra);
            g.DrawRectangles(Pens.Black, ra);
        }

        void Render()
        {
            if (_bitmap == null)
                return;

            Graphics g = Graphics.FromImage(_bitmap);
            g.Clear(Color.White);

            RenderGrid(g);
            RenderChart(g);
            RenderControlPoints(g);

            pictureBox1.Image = _bitmap;
        }

        IEnumerable<InterpolationDescription> InterpolationDescriptionsList()
        {
            List<InterpolationDescription> funcs = new List<InterpolationDescription>();
            InterpolationDescription id;

            id = new InterpolationDescription
            {
                function = InterpolationEx.Lagrang,
                T1 = -20,
                T2 = 230,
                Description = "Полином Лагранжа",
                X = DataXForInterpolation,
                Y = DataYForInterpolation
            };

            funcs.Add(id);

            id = new InterpolationDescription
            {
                function = InterpolationEx.LinearTrend,
                T1 = -20,
                T2 = 290,
                Description = "Линейный тренд",
                X = DataXForInterpolation,
                Y = DataYForInterpolation
            };

            funcs.Add(id);

            return funcs;
        }

        Bitmap CreateBackground(int width, int height)
        {
            return (width > 0 || height > 0) ? new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format24bppRgb) : null;
        }

        #endregion

        private void frmMain_Load(object sender, EventArgs e)
        {
            pictureBox1.BackColor = Color.White;
            _bitmap = CreateBackground(pictureBox1.Width, pictureBox1.Height);

            var funcs = InterpolationDescriptionsList();
            cmbFuncs.Items.AddRange(funcs.ToArray());
            cmbFuncs.SelectedIndex = 0;

            InterpolationDescription fd = cmbFuncs.SelectedItem as InterpolationDescription;
            _curve = GetInterpolation2D(fd, pictureBox1.Width, pictureBox1.Height);
        }

        private void pictureBox1_SizeChanged(object sender, EventArgs e)
        {
            _bitmap = CreateBackground(pictureBox1.Width, pictureBox1.Height);
            InterpolationDescription fd = cmbFuncs.SelectedItem as InterpolationDescription;
            _curve = GetInterpolation2D(fd, pictureBox1.Width, pictureBox1.Height);
        }

        private void frmMain_Paint(object sender, PaintEventArgs e)
        {
            Render();
        }

        private void cmbFuncs_SelectedIndexChanged(object sender, EventArgs e)
        {
            InterpolationDescription fd = cmbFuncs.SelectedItem as InterpolationDescription;
            _curve = GetInterpolation2D(fd, pictureBox1.Width, pictureBox1.Height);
            Render();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Close();
        }
    }

    class InterpolationDescription
    {
        public InterpolationFunc function { get; set; }
        public double[] X { get; set; }
        public double[] Y { get; set; }
        public string Description { get; set; }
        public double T1 { get; set; }
        public double T2 { get; set; }

        public override string ToString()
        {
            return Description;
        }
    }    
}


